#include <arpa/inet.h>
#include <net/ethernet.h>
#include <netdb.h>
#include <netinet/ether.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <pthread.h>
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <syslog.h>

#include <iostream>
#include <map>
#include <list>
#include <string>

extern "C" {
#include <pcap.h>
}

using namespace std;

#include "sqlwrite.h"
#include "ip.h"
#include "sll.h"
#include "dns.h"
#include "prio.h"
#include "monlist.h"

DnsRecord::DnsRecord(struct in_addr ip, string& hostName, int serial, time_t last_access) :
	ip_m(ip), hostName_m(hostName), serial_m(serial), last_access_m(last_access), byDate_m(0) {

}

DnsRecord::DnsRecord(struct in_addr ip, string hostName) :
	ip_m(ip), hostName_m(hostName), serial_m(0), last_access_m(0), byDate_m(0) {
}

void DnsCache::FlushOld()
{
	time_t	now = time((time_t *)0);

	pthread_mutex_lock(&mutex_);
	for (DnsRecordsByDateReverseIter iter = recordsByDate_m.rbegin(); iter != recordsByDate_m.rend(); iter++) {
		DnsRecord *dnsRec = *iter;
		if (dnsRec->last_access_m < now - expiryTime_m) {
			// delete from map
			ipToRecord_m.erase(dnsRec);
			// delete from list
			recordsByDate_m.erase(dnsRec->byDate_m);
			delete dnsRec;
		} else 
			break;
	}
	pthread_mutex_unlock(&mutex_);
}

int	DnsCache::FindHostFromIP(const struct in_addr& ip, const char *mac) {
	char			buf[8192];  // make non local or malloc
	uint32_t addr;
	struct hostent  hpb;
	struct hostent *hp = &hpb;
	int	res;
	int	err;
	string	resolvedName;
	char	inet_char[inet_to_stringSize];

	memset(buf, 0, sizeof (buf));
	memset(&hpb, 0, sizeof (hpb));
	// gethost by name using thread safe version
	res = gethostbyaddr_r((char *)&ip, sizeof (struct in_addr),
								 AF_INET, 
								 &hpb,
								 buf, sizeof(buf),
								 &hp, &err);
	inet_to_char(ip, inet_char);
	if (errno == 0) {
		if (hp == 0) {
			resolvedName = inet_char;
			//cerr << "resolved name '" << resolvedName << "' returned null host entry, res was " << res << endl;
		} else {
		// convert to lower case
			for (char *cp = hp->h_name; *cp; cp++)
				*cp = tolower(*cp);
			resolvedName = hp->h_name;
			//cerr << "resolved name '" << resolvedName << endl;
		}
	} else if (errno == ENOENT) {
		resolvedName = inet_char;
	} else {
		syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "gethostbyaddr_r: %s looking for %s",
		strerror(errno), inet_char);
		resolvedName = inet_char;
	}
	// create temporary Dns record for lookup
	DnsRecord *inDns = new DnsRecord(ip, resolvedName);
	DnsRecord *newDns;

	pthread_mutex_lock(&mutex_);
	DnsRecordMapIter		dnsRec = ipToRecord_m.find(inDns);

	if (dnsRec == ipToRecord_m.end()) {
		newDns = new DnsRecord(*inDns);
		newDns->serial_m = SQLGetDNS(resolvedName, ip, mac);
		ipToRecord_m[inDns] = newDns;
	} else { // found
		newDns = (*dnsRec).second;
		delete inDns;
		recordsByDate_m.erase(newDns->byDate_m);
	}
	newDns->last_access_m = time((time_t *)0);
	recordsByDate_m.push_front(newDns);
	newDns->byDate_m = recordsByDate_m.begin();
	pthread_mutex_unlock(&mutex_);
	return newDns->serial_m;
}

char *inet_to_char(struct in_addr ip, char *out) {
	unsigned char *ucp = (unsigned char *)&ip;
	sprintf(out, "%d.%d.%d.%d",
		ucp[0] & 0xff,
		ucp[1] & 0xff,
		ucp[2] & 0xff,
		ucp[3] & 0xff);
	return out;
}
